//! Headers which are parts of application.
#include "HDRLightsDemo.hpp"
#include "HDRDemoConstants.hpp"
#include "Extensions/ExtensionsLoader.hpp"
#include "ScreenSystem/DemoSceneScreen.hpp"
#include "ScreenSystem/ScreenManager.hpp"
#include "SceneManager.hpp"
#include "Mouse/MouseManager.hpp"
#include "ResourceManagers/TextureManager.hpp"
#include "ResourceManagers/HDRManager.hpp"
#include "Mathematics/MMatrix4.hpp"
#include "Mathematics/MVector3D.hpp"
#include "LookAtMatrix.hpp"
#include "ProjectionMatrix.hpp"

//! Headers for asserts and time and math functions.
#include <time.h>
#include <math.h>
#include <assert.h>

//! Headers for external functionality ( text files loading for shaders /
//! FreeImage library providing loading of graphic files like .PNG, .BMP, etc. ).
#include "ResourceManagers/textfile.h"
#include "ResourceManagers/FreeImage.h"

#include "ResourceManagers/TextureObject.hpp"

//! Headers concerning OpenGL.
//#include <GL/glut.h>
//#include <GL/glext.h>

#include "Globals.hpp"

using namespace std;


//! Be aware that LIGHTS_IN_TEX and MULTIPLE_LIGHTS_ON_REGISTERS should not be used together.
//#define _NO_SHADERS
//#define MULTIPLE_LIGHTS_ON_REGISTERS
#define LIGHTS_IN_TEXTURE


HDRLightsDemo::HDRLightsDemo(int argc, char **argv):
m_angle( 0.0f ),
m_x( 0.0f ),
m_y( 15.0f ),
m_z( 20.0f )
{
	m_argc = argc;
	m_argv = argv;

	InitGLUT();
	InitDemo();
}

HDRLightsDemo::~HDRLightsDemo()
{
	SceneManager::GetInstance()->UnregisterAllEntities();
	HDRManager::destroyInstance();
	delete m_demoScene;
}

bool HDRLightsDemo::InitDemo()
{
	//! Create ExtensionsLoader.
	ExtensionsLoader::GetInstance();

	//! Set light parameters.
	GLfloat LightAmbient[] = { 0.2f, 0.2f, 0.2f, 1.0f },
			LightDiffuse[] = { 1.0f, 1.0f, 1.0f, 1.0f },
			LightSpecular[] = { 1.0f, 1.0f, 1.0f, 1.0f },
			LightPosition[] = { 3.5f, 12.0f, 13.0f };

	glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmbient );
	glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiffuse );
	glLightfv(GL_LIGHT0, GL_SPECULAR, LightSpecular );
	glLightfv(GL_LIGHT0, GL_POSITION, LightPosition );

	//! Initialise FreeImage library with using only local plugins.
	FreeImage_Initialise( true );

	//! Create managers.
	SceneManager::GetInstance();
	CameraManager::GetInstance();
	ScreenManager::GetInstance();
	MouseManager::GetInstance();
	TextureManager::createInstance();
	HDRManager::createInstance();

	InitDepthTexture();
	//! Create screen.
	m_demoScene = new DemoSceneScreen;

#ifndef _NO_SHADERS
	LoadShaders();
#endif

//! preps for shadow mapping
	//cout << "GL Proj matrix: " << endl;
	//printf( " %.2f  %.2f  %.2f  %.2f \n %.2f  %.2f  %.2f  %.2f \n %.2f  %.2f  %.2f  %.2f \n %.2f  %.2f  %.2f  %.2f \n", proj[0], proj[1], proj[2], proj[3],
	//	proj[4], proj[5], proj[6], proj[7], proj[8], proj[9], proj[10], proj[11], proj[12], proj[13], proj[14], proj[15] );

    //! CAMERA MATRICES
	ProjectionMatrix* projMat = new ProjectionMatrix( 45.0f, (float)800/600, 1.0f, 50.0f );
	MMatrix4 projection = projMat->GetProjectionMatrix();

	Globals::GetInstance()->SetCameraMatrix( PROJECTION_MATRIX, projection );
	delete projMat; projMat = NULL;

	CameraObject* camera = CameraManager::GetInstance()->GetActiveCamera();
	MVector3D pos = camera->GetPosition();
	MVector3D tar = camera->GetTarget();
	MVector3D up  = camera->GetUpVec();

	LookAtMatrix* lookAt = new LookAtMatrix( pos, tar, up );
	MMatrix4 modelViewMatrix = lookAt->GetModelViewMatrix();
	Globals::GetInstance()->SetCameraMatrix( MODELVIEW_MATRIX, modelViewMatrix );
	camera = NULL;
	delete lookAt; lookAt = NULL;

	//! LIGHT MATRICES
	projMat = new ProjectionMatrix( 45.0f, 1.0f, 1.0f, 25.0f );
	projection = projMat->GetProjectionMatrix();

	Globals::GetInstance()->SetLightMatrix( PROJECTION_MATRIX, projection );
	delete projMat; projMat = NULL;


	glMatrixMode( GL_PROJECTION_MATRIX );
	glLoadMatrixd( Globals::GetInstance()->GetCameraMatrix( PROJECTION_MATRIX ) );

	//glMatrixMode( GL_MODELVIEW_MATRIX );
	//glLoadMatrixd( Globals::GetInstance()->GetCameraMatrix( MODELVIEW_MATRIX ) );

	return true;
}

void HDRLightsDemo::InitGLUT()
{
	//! Initialises GLUT, enables some OpenGL states and sets callback functions.
	glutInit( &m_argc, m_argv );
	glutInitDisplayMode( GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA );

	glutInitWindowPosition( WINDOW_X_POS, WINDOW_Y_POS );
	glutInitWindowSize( WINDOW_WIDTH , WINDOW_HEIGHT );
	glutCreateWindow( WINDOW_CAPTION );

	glutMouseFunc( &MouseManager::GetInstance()->ProcessMouseClicks );
	glutMotionFunc( &MouseManager::GetInstance()->ProcessMouseMotion );

	glEnable( GL_DEPTH_TEST );

	//glutDisplayFunc( &SceneManager::GetInstance()->UpdateScene );
	//glutIdleFunc( &SceneManager::GetInstance()->UpdateScene );
	glutDisplayFunc( &UpdateDemo );
	glutIdleFunc( &UpdateDemo );

	glutReshapeFunc( &HDRLightsDemo::ResizeWindow );

	//glutDisplayFunc( &SceneManager::GetInstance()->UpdateScene );
	glutDisplayFunc( &UpdateDemo );

#ifdef _NO_SHADERS
	glEnable( GL_LIGHTING );
	glEnable( GL_LIGHT0 );
	glShadeModel( GL_SMOOTH );
#endif

	GLfloat LightPosition[3];
	LightPosition[0] = 3.5;
	LightPosition[1] = 10.0f;
	LightPosition[2] = 13.0f;

	glLightfv(GL_LIGHT0, GL_POSITION, LightPosition );

}

void HDRLightsDemo::GoSleep( clock_t wait )
{
	//! Puts application into sleep for specified time.
	clock_t goal;
	goal = wait + clock();
	while( goal > clock() )
	;
}
void HDRLightsDemo::LoadShaders()
{
	ProjectionMatrix* projection = new ProjectionMatrix( 45.0f, 1.0f, 1.0f, 25.0f );

	LoadShader( LIGHTING_SHADER );
	LoadShader( DEPTH_MAP_GENERATOR );
	LoadShader( SHADOW_TEST );

//	InitDepthTexture();
	assert( SetProjMatrixForShader( ExtensionsLoader::GetInstance()->GetShader( SHADOW_TEST ), "lproj", projection ) );
	//SetTextureForShader( ExtensionsLoader::GetInstance()->GetShader( DEPTH_MAP_GENERATOR ), "depthMap" );
	//assert( SetTextureForShader( ExtensionsLoader::GetInstance()->GetShader( LIGHTING_SHADER ), "shadowMap", 1 ) );
	assert( SetTextureForShader( ExtensionsLoader::GetInstance()->GetShader( SHADOW_TEST ), "depthMap", 0 ) );
	//assert( SetTextureForShader( ExtensionsLoader::GetInstance()->GetShader( DEPTH_MAP_GENERATOR ), "lightDirs", 0 ) );

	assert( SetProjMatrixForShader( ExtensionsLoader::GetInstance()->GetShader( DEPTH_MAP_GENERATOR ), "lightProjection", projection ) );
	//assert( SetProjMatrixForShader( ExtensionsLoader::GetInstance()->GetShader( LIGHTING_SHADER ), "lightProjection", projection ) );
//	SetProjMatrixForShader( ExtensionsLoader::GetInstance()->GetShader( SHADOW_TEST ), "lproj", projection );

	GenerateLights();
/////////////////////////////////////
//		GET LOC FOR MV_INV_MATRIX
/////////////////////////////////////
	ExtensionsLoader::glUseProgramObjectARB( ExtensionsLoader::GetInstance()->GetShader( SHADOW_TEST ) );

	*(ExtensionsLoader::GetInstance()->GetMVInvLoc()) = ExtensionsLoader::glGetUniformLocationARB( ExtensionsLoader::GetInstance()->GetShader( SHADOW_TEST ), "camIMV" );

	assert( *(ExtensionsLoader::GetInstance()->GetMVInvLoc()) >= 0 );

	MMatrix4* invMatrix = new MMatrix4;
	GLfloat* mvMat = (GLfloat*)Globals::GetInstance()->GetCameraMatrix(MODELVIEW_MATRIX);
	invMatrix->InverseMatrix(MMatrix4( mvMat[0], mvMat[1], mvMat[2], mvMat[3], mvMat[4],
		mvMat[5], mvMat[6], mvMat[7], mvMat[8], mvMat[9], mvMat[10], mvMat[11],
		mvMat[12], mvMat[13], mvMat[14], mvMat[15]) );

	ExtensionsLoader::glUniformMatrix4fvARB( *(ExtensionsLoader::GetInstance()->GetMVInvLoc()), 1, GL_FALSE, invMatrix->m_matrix );	

/////////////////////////////////////
	ExtensionsLoader::glUseProgramObjectARB( ExtensionsLoader::GetInstance()->GetShader( LIGHTING_SHADER ) );
}

void HDRLightsDemo::CreateScene()
{

}

void HDRLightsDemo::PrintInfoLog( GLhandleARB obj )
{
	//! Prints log about shader's initialisation.

    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;

	ExtensionsLoader::glGetObjectParameterivARB( obj, GL_OBJECT_INFO_LOG_LENGTH_ARB, &infologLength );

    if (infologLength > 0)
    {
        infoLog = ( char * )malloc( infologLength );
		ExtensionsLoader::glGetInfoLogARB( obj, infologLength, &charsWritten, infoLog );
		printf( "%s\n",infoLog );
        free( infoLog );
    }
}

void HDRLightsDemo::UpdateDemo()
{
	clock_t tick = clock();

	SceneManager::GetInstance()->UpdateScene( tick );
	CameraManager::GetInstance()->Update( tick );
}

void HDRLightsDemo::InitDepthTexture()
{
//! Moved to ShadowMapFBO class.
}

void HDRLightsDemo::GenerateLights()
{
	srand( (unsigned)time( NULL ) );

	int index; //liczba swiatel

#ifndef LIGHTS_IN_TEXTURE
	float* lpos;
#else
	float* lpos;

	lpos = new float [ /*16 * 8*/32 * 32 * 3 ];
	float fShift = RAND_MAX/2;//(RAND_MAX+1)/(RAND_MAX/2);
	MVector3D light;

	//! Light vector is normalized and then trasnformed from scope [-1,1] to scope [0,1].
	//! It is done by transformation to [0,2] by adding 1.0 and thereafter dividing by 2.
	//! Inverse tranformations should be done it two places: inside of shader before using light vectors
	//! and before building MODELVIEW matrices for lights while rendering shadow maps.
	//! It must be secured that modelview matrices in shader and during shadow maps rendering are the same.
	//! Since in modelview matrices target and eye positions are needed, light vector should be multiplied by
	//! some constant (f.e. 10.0). This will put all eye positions on sphere with radius equal to used constant.
	for( int i = 0; i < /*(16*8*3)*/(32*32*3) - 3; i+= 3 )
	{
		//! calculate values on whole scope and finally normalize them
		//float u = ((double)rand() / (RAND_MAX + 1));// - (RAND_MAX+1)/(RAND_MAX/2);
		
		light.x = (double)rand() / fShift *10;

		//u = ((double)rand() / (RAND_MAX + 1));// - (RAND_MAX+1)/(RAND_MAX/2);
		light.y = (double)rand() / fShift *10;

		//u = ((double)rand() / (RAND_MAX + 1));// - (RAND_MAX+1)/(RAND_MAX/2);
		light.z = (double)rand() / fShift *10;

		/*light.Normalize();

		lpos[i]   = (light.x+1.0f) * 0.5;
		lpos[i+1] = (light.y+1.0f) * 0.5;
		lpos[i+2] = (light.z+1.0f) * 0.5;*/
	lpos[i] = light.x;
	lpos[i+1] = light.y;
	lpos[i+2] = light.z;
	}

	light.x = 3.0f; light.y = 6.0f; light.z = 3.0f;
	lpos[0] = light.x;
	lpos[1] = light.y;
	lpos[2] = light.z;
	/*light.Normalize();
	lpos[0] = (light.x+1.0f) * 0.5;
	lpos[1] = (light.y+1.0f) * 0.5;
	lpos[2] = (light.z+1.0f) * 0.5;*/


	//light.x = 0.0f; light.y = 15.0f; light.z = 5.0f;
	/*light.Normalize();
	lpos[3] = (light.x+1.0f) * 0.5;
	lpos[4] = (light.y+1.0f) * 0.5;
	lpos[5] = (light.z+1.0f) * 0.5;*/
	light.x = -3.0f; light.y = 6.0f; light.z = 3.0f;
	lpos[3] = light.x;
	lpos[4] = light.y;
	lpos[5] = light.z;

	light.x = 1.0f; light.y = 6.0f; light.z = -4.0f;
	lpos[6] = light.x;
	lpos[7] = light.y;
	lpos[8] = light.z;

	index = 32*32;
#endif

	GLint loc1;

#if !defined MULTIPLE_LIGHTS_ON_REGISTERS && !defined LIGHTS_IN_TEXTURE
	index = 1;
	lpos = new float[ 3 * index ];
	lpos[0] = 0.0f; lpos[1] = 5.0f; lpos[2] = 13.0f;
#endif

#if defined MULTIPLE_LIGHTS_ON_REGISTERS && !defined LIGHTS_IN_TEXTURE
	index = 150;
	lpos = new float[ 3 * index ];

	int i = 0;
	while( i < 3*index - 2 )
	{
		float u = (double)rand() / (RAND_MAX + 1) * (250 - (-250)) + (-250);
		lpos[i]   = u;

		u = (double)rand() / (RAND_MAX + 1) * (250 - (-250)) + (-250);
		lpos[i+1] = u;

		u = (double)rand() / (RAND_MAX + 1) * (250 - (-250)) + (-250);
		lpos[i+2] = u;

		cout.precision(3);
		cout << "L[" << i << "]:" << lpos[i]  << "   ";
		cout << "L[" << i+1 << "]:" << lpos[i+1] << "   ";
		cout << "L[" << i+2 << "]:" << lpos[i+2]  << "\n";

		i += 3;
		//GoSleep(200);
	}
#endif

#ifdef LIGHTS_IN_TEXTURE
	glGenTextures( 1, Globals::GetInstance()->GetSpecialTexture( LIGHT_POSITIONS ) );
	
	glBindTexture( GL_TEXTURE_2D, *(Globals::GetInstance()->GetSpecialTexture( LIGHT_POSITIONS )) );
	glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB16F_ARB, /*16, 8,*/8, 8, 0, GL_RGB, GL_FLOAT, lpos );
	
	//TextureManager::GetInstance()->LoadTexture( WALL_TEST_TEX );
	//TextureObject* tex = (TextureObject*)TextureManager::GetInstance()->GetTexture( WALL_TEST_TEX );
	
	//glTexImage2D( GL_TEXTURE_2D, 1, GL_RGB, 16,8, 0, GL_RGB, GL_UBYTE, tex->GetTextureID() );
	
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
#endif


#ifndef LIGHTS_IN_TEXTURE
	loc1 = ExtensionsLoader::glGetUniformLocationARB( ExtensionsLoader::GetInstance()->GetShader( LIGHTING_SHADER),"lpos" );
	ExtensionsLoader::glUniform3fvARB( loc1, index,lpos );

	loc2 = ExtensionsLoader::glGetUniformLocationARB( ExtensionsLoader::GetInstance()->GetShader( LIGHTING_SHADER), "index" );
	ExtensionsLoader::glUniform1iARB( loc2, index );
#else
	loc1 = ExtensionsLoader::glGetUniformLocationARB( ExtensionsLoader::GetInstance()->GetShader( LIGHTING_SHADER), "lpos" );
	ExtensionsLoader::glUniform1iARB( loc1, 0 );

	ExtensionsLoader::glUseProgramObjectARB( ExtensionsLoader::GetInstance()->GetShader( SHADOW_TEST ) );
	loc1 = ExtensionsLoader::glGetUniformLocationARB( ExtensionsLoader::GetInstance()->GetShader( SHADOW_TEST ), "lights" );
	ExtensionsLoader::glUniform1iARB( loc1, 1 );
#endif

	LookAtMatrix* lookAt;

	MVector3D tar = MVector3D( 0.0f, 0.0f, 0.0f );
	MVector3D up = MVector3D( 0.0f, 1.0f, 0.0f );
	MVector3D pos;

	for( int i = 0; i < 192; i+=3 )
	{
		pos = MVector3D( lpos[i], lpos[i+1], lpos[i+2]);
			/*lpos[i]*2.0 -1.0, lpos[i+1]*2.0 -1.0, lpos[i+2]*2.0 -1.0 );*/
		//pos = pos * 15;

		/*if(i == 0)
		{
		pos.x = 2.0f; pos.y = 10.0f; pos.z = 5.0f;
		}
		else
		{
			pos.x = 0.0f; pos.y = 15.0f; pos.z = 5.0f;
		}*/
		lookAt = new LookAtMatrix( pos, tar, up );
		
		//modelViewMatrix = lookAt->GetModelViewMatrix();
		//Globals::GetInstance()->SetLightMatrix( MODELVIEW_MATRIX, modelViewMatrix );
		Globals::GetInstance()->AddLookAtMatrix( lookAt );
		//delete lookAt; lookAt = NULL;
	}

	mvMatricesVec mvec = Globals::GetInstance()->GetLookAtMaticesVector();

	mvMatricesVec::iterator dupa = mvec.begin();

	lookAt = *dupa;
	MMatrix4 modelViewMatrix = lookAt->GetModelViewMatrix();
	Globals::GetInstance()->SetLightMatrix( MODELVIEW_MATRIX, modelViewMatrix );

	delete[] lpos;
}

void HDRLightsDemo::LoadShader( enum Shader whichShader )
{
	char *vs = NULL, *fs = NULL;
	int maxFragmentALUOps;

	m_v  = ExtensionsLoader::glCreateShaderObjectARB( GL_VERTEX_SHADER_ARB );
	m_f  = ExtensionsLoader::glCreateShaderObjectARB( GL_FRAGMENT_SHADER_ARB);

	//sprawdzenie ilosci operacji ALU wykonywanych prze karte w jednym programie
	ExtensionsLoader::glGetProgramivARB( GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB, &maxFragmentALUOps );
	printf("GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB: %d\n", maxFragmentALUOps );

	//! Read appropriate shader file.
	switch( whichShader )
	{
	case LIGHTING_SHADER:
		vs  = textFileRead( LIGHTING_VERTEX_PROGRAM_SOURCE );
		fs  = textFileRead( LIGHTING_FRAGMENT_PROGRAM_SOURCE );
		break;

	case DEPTH_MAP_RENDERER:
		vs  = textFileRead( DEPTH_MAP_REND_VERTEX_PROGRAM_SRC );
		fs  = textFileRead( DEPTH_MAP_REND_FRAGMENT_PROGRAM_SRC );
		break;

	case DEPTH_MAP_GENERATOR:
		vs  = textFileRead( DEPTH_MAP_GEN_VERTEX_PROGRAM_SRC );
		fs  = textFileRead( DEPTH_MAP_GEN_FRAGMENT_PROGRAM_SRC );
		break;

	case SHADOW_TEST:
		vs = textFileRead( SHADOW_TEST_VERTEX_PROGRAM_SRC );
		fs = textFileRead( SHADOW_TEST_FRAGMENT_PROGRAM_SRC );
	}

	const char * vv  = vs;
	const char * ff  = fs;

	ExtensionsLoader::glShaderSourceARB( m_v,  1, &vv, NULL );
	ExtensionsLoader::glShaderSourceARB( m_f,  1, &ff, NULL );

	free( vs ); free( fs );

	//kompilacja shaderow
	ExtensionsLoader::glCompileShaderARB( m_v );
	ExtensionsLoader::glCompileShaderARB( m_f );

	PrintInfoLog( m_v );
	PrintInfoLog( m_f );

	m_shader = ExtensionsLoader::glCreateProgramObjectARB();
	ExtensionsLoader::glAttachObjectARB( m_shader, m_v );
	ExtensionsLoader::glAttachObjectARB( m_shader, m_f );

	ExtensionsLoader::glLinkProgramARB( m_shader );
	PrintInfoLog( m_shader );

	switch( whichShader )
	{
	case LIGHTING_SHADER:
		ExtensionsLoader::GetInstance()->SetShader( m_shader, LIGHTING_SHADER );
		break;

	case DEPTH_MAP_RENDERER:
		ExtensionsLoader::GetInstance()->SetShader( m_shader, DEPTH_MAP_RENDERER );
		break;

	case DEPTH_MAP_GENERATOR:
		ExtensionsLoader::GetInstance()->SetShader( m_shader, DEPTH_MAP_GENERATOR );
		break;

	case SHADOW_TEST:
		ExtensionsLoader::GetInstance()->SetShader( m_shader, SHADOW_TEST );
		break;
	}
}

bool HDRLightsDemo::SetTextureForShader(GLhandleARB shader, std::string texName, int texturingUnit )
{
	ExtensionsLoader::glUseProgramObjectARB( shader );

	GLint texLoc;

	texLoc = ExtensionsLoader::glGetUniformLocationARB( shader, texName.c_str() );

	if( texLoc < 0 )
		return false;

	ExtensionsLoader::glUniform1iARB( texLoc, texturingUnit );

	return true;
}

bool HDRLightsDemo::SetProjMatrixForShader(GLhandleARB shader, std::string matrixName, ProjectionMatrix* projMat)
{
	ExtensionsLoader::glUseProgramObjectARB( shader );

	GLint matrixLoc;
	GLfloat *proj = new GLfloat[16];


	MMatrix4 mat = projMat->GetProjectionMatrix();

	proj[0] = mat.m_matrix[0]; proj[1] = mat.m_matrix[1]; proj[2] = mat.m_matrix[2]; proj[3] = mat.m_matrix[3];
	proj[4] = mat.m_matrix[4]; proj[5] = mat.m_matrix[5]; proj[6] = mat.m_matrix[6]; proj[7] = mat.m_matrix[7];
	proj[8] = mat.m_matrix[8]; proj[9] = mat.m_matrix[9]; proj[10] = mat.m_matrix[10]; proj[11] = mat.m_matrix[11];
	proj[12] = mat.m_matrix[12]; proj[13] = mat.m_matrix[13]; proj[14] = mat.m_matrix[14]; proj[15] = mat.m_matrix[15];

	matrixLoc = ExtensionsLoader::glGetUniformLocationARB( shader, matrixName.c_str() );

	if( matrixLoc < 0 )
		return false;

	ExtensionsLoader::glUniformMatrix4fvARB( matrixLoc, 1, GL_FALSE, proj );

	return true;
}